<template>
  <div
    class="mx-map"
    :id="mapId"
    param="tileUrl:googleSate;bounds:67.5 0 157.5 67.5;maptype:google;backgroundColor:#F5F3F0;zoom:4;tileStretchPixel:2;sliceZoom:5;maxZoom:21;minZoom:5;fullZoom:18;mapTilesWidth:2048;mapTilesHeight:1536;errorImg:static/webgis/mxImg/blank.png;">
    <div
      class="mx-map-content"
      :style="wrapStyle">
      <component
        :is="getLayerComponent(layer)"
        v-for="(layer, name) in options.layers"
        v-if="createLayerList.indexOf(name) > -1"
        :show="activeLayerList.indexOf(name) > -1"
        :activeLayerList="activeLayerList"
        :key="name"
        :name="name"
        :map="map"
        :layer="layer"
        :options="options"
        :sources="sources"
        :zoom="zoom"
        :category="getCategory(name)"
        @create="onLayerCreate"
        @liveQuery="onLiveQuery"/>
      <mx-info
        v-if="showInfoWindow"
        :show="activeLayerList.indexOf(infoWindowOptions.name) > -1"
        :sources="sources"
        :map="map"
        :zoom="zoom"
        :category="getCategory(infoWindowOptions.name)"
        :options="infoWindowOptions"
        ref="infoWindow"/>
    </div>
    <slot v-if="ready"></slot>
  </div>
</template>

<script type="text/ecmascript-6">
  import mxInfo from './info/index.vue'
  import common, { MergeRequest, Emitter, EventName, ComponentName } from './common.js'
  import layerComponents from './layer/index.js'

  const POSITION = { top: 0, left: 0 }

  export default {
    componentName: ComponentName.map,
    components: { mxInfo, ...layerComponents },
    mixins: [Emitter],
    props: {
      options: {
        type: Object
      }
    },
    data() {
      return {
        mapId: Date.now(),
        ready: false,
        map: null,
        sources: {},
        zoom: 4,
        showInfoWindow: false,
        infoWindowOptions: null,
        wrapStyle: { ...POSITION },
        createLayerList: [],
        activeLayerList: []
      }
    },
    created() {
      this.request = new MergeRequest()
      this.initEvent()
    },
    mounted() {
      this.initMap()
    },
    methods: {
      getLayerComponent(layer) {
        return 'mx-layer-' + (layer.layout || 'div')
      },
      onLiveQuery({ name, event, object }) {
        if (!this.sources[name]) return
        let index = this.sources[name].findIndex(v => v.id === object.id)
        if (event === 'create') {
          this.sources[name].push(object)
        } else if (event === 'update' && index > -1) {
          this.sources[name].splice(index, 1, object)
          if (
            this.showInfoWindow &&
            this.infoWindowOptions.name === name &&
            this.infoWindowOptions.prop &&
            this.infoWindowOptions.prop.id === object.id
          ) {
            this.infoWindowOptions.prop = object
          }
        } else if (event === 'delete' && index > -1) {
          this.sources[name].splice(index, 1)
          if (
            this.showInfoWindow &&
            this.infoWindowOptions.name === name &&
            this.infoWindowOptions.prop &&
            this.infoWindowOptions.prop.id === object.id
          ) {
            this.showInfoWindow = false
          }
        }
      },
      async onLayerCreate({ name, layer, complete }) {
        try {
          let source = layer.source || [name]
          let p = []
          source.forEach(item => {
            let options = this.options.sources[item]
            if (options) {
              if (this.sources[item]) {
                p.push({[item]: this.sources[item]})
              } else {
                let fn = this.request.run(item, common.getSource(item, options))
                p.push(fn)
              }
            }
          })
          let res = await Promise.all(p)
          let obj = {}
          res.forEach(item => Object.assign(obj, item))
          if (layer.mergeSource) {
            obj = layer.mergeSource(obj)
          }
          this.sources = Object.assign({}, this.sources, obj)
        } catch (e) {
        }
        complete && complete()
      },
      utilBroadcastUpdatePosition(e) {
        this.broadcast(ComponentName.marker, EventName.updatePosition, e)
        this.broadcast(ComponentName.info, EventName.updatePosition, e)
        this.wrapStyle = { ...POSITION }
      },
      utilSetActiveAndCreateLayers() {
        let active = this.getActiveLayers()
        this.activeLayerList = active
        this.createLayerList = Array.from(new Set([].concat(active, this.createLayerList)))
      },
      utilUpdateInfoWindowPostion() {
        setTimeout(() => {
          if (!this.$refs.infoWindow) return
          let _map = this.$el.getBoundingClientRect()
          let _panel = this.$refs.infoWindow.$refs.window.getBoundingClientRect()
          let dx = 0
          let dy = 0
          let offsetX = 20
          let offsetY = 70
          if (_panel.x < 0) {
            dx = -_panel.x + _map.x + offsetX
          } else if ((_panel.left + _panel.width - _map.width) > 0) {
            dx = _panel.left + _panel.width - _map.width + offsetX
            dx = -dx
          }
          if (_panel.y < 0) {
            dy = -_panel.y + _map.y + offsetY
          } else if ((_panel.top + _panel.height - _map.height) > 0) {
            dy = _panel.top + _panel.height - _map.height + offsetY
            dy = -dy
          }
          dx = this.map.pixelDistToPointDist(dx)
          dy = this.map.pixelDistToPointDist(dy)
          let center = this.map.getCenter()
          this.map.setCenter(new mxLib.Point(center.x - dx, center.y + dy))
        }, 0)
      },
      getActiveLayers() {
        if (!this.options.active || !this.options.active.length) return []
        return this.options.active.map(item => item.split('.')[0])
      },
      getCategory(name) {
        if (!this.options.active || !this.options.active.length) return []
        return this.options.active.reduce((results, item) => {
          let [_name, _category] = item.split('.')
          if (_name === name && _category) { results.push(_category) }
          return results
        }, [])
      },
      initEvent() {
        this.$on(EventName.openInfoWindow, options => {
          this.showInfoWindow = false
          this.$nextTick(() => {
            this.infoWindowOptions = options
            this.showInfoWindow = true
            this.utilUpdateInfoWindowPostion()
          })
        })
        this.$on(EventName.closeInfoWindow, () => this.showInfoWindow = false)
      },
      async initMap() {
        let center = [119.5261617188496, 36.8907983855454]
        const fn = (e) => {
          let left = this.map.panesParentDiv.style.left
          let top = this.map.panesParentDiv.style.top
          this.wrapStyle = { left, top }
        }
        this.map = new mxLib.Map(this.mapId)
        this.map.addEventListener(EventName.zoomend, () => (this.zoom = this.map.getZoom()))
        this.map.addEventListener(EventName.zoomend, this.utilBroadcastUpdatePosition)
        this.map.addEventListener(EventName.moveend, this.utilBroadcastUpdatePosition)
        this.map.addEventListener(EventName.dragend, this.utilBroadcastUpdatePosition)
        this.map.addEventListener(EventName.resize, this.utilBroadcastUpdatePosition)
        this.map.addEventListener(EventName.dragging, fn)
        this.map.addEventListener(EventName.moving, fn)
        this.map.setZoom(15)
        this.map.setCenter(new mxLib.Point(...center))
        this.ready = true
        this.utilSetActiveAndCreateLayers()
      }
    },
    watch: {
      'options.active'() {
        this.utilSetActiveAndCreateLayers()
      }
    }
  }
</script>

<style type="text/stylus" lang="stylus" rel="stylesheet/stylus">
  .mx-map {
    position: relative;
    width: 100%;
    height: 100%;
    overflow: hidden;
    &-content {
      position: absolute;
      left: 0px;
      top: 0px;
      overflow: visible;
      z-index: 10;
    }
  }
</style>
